﻿/*    
Nemulation: A .NET based framework/platform for virtualization applications
Copyright (C) 2011 Bryan Perris

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Reflection;
using System.Security.Policy;
using System.Threading;
using NEmu.Services;

namespace NEmuVirtualHost.Configuration
{
    [Serializable]
    public sealed class HostConfigReader : IConfigurationSystem, IDisposable, IDictionary<string, string>
    {
        FileStream m_SchemaFile;
        FileStream m_ConfigFile;
        XmlDocument m_XmlConfigDocument;
        private bool _disposed;

        public HostConfigReader()
        {
            Init();
        }

        ~HostConfigReader()
        {
            Dispose(false);
        }

        private XmlNodeList GetSettingElements()
        {
            if (m_XmlConfigDocument != null)
            {
                XmlNodeList elements = m_XmlConfigDocument.DocumentElement.GetElementsByTagName("settings");

                if (elements.Count > 0)
                {
                    XmlNode settingsElement = elements[0];
                    return settingsElement.ChildNodes;
                }
            }

            return null;
        }

        private void OnXmlValidate(object sender, ValidationEventArgs e)
        {
            throw new XmlException("XML file does not match schema rules");
        }

        private void OnSchemaValidate(object sender, ValidationEventArgs e)
        {
            throw new XmlSchemaException(e.Message);
        }

        private string GetSafeAttributeValue(XmlNode node)
        {
            if (node != null)
            {
                return node.Value;
            }
            else
            {
                return String.Empty;
            }

        }

        public List<string> GetAssemblyReferenceList()
        {
            // throw an exception if the class has no Xml document
            if (m_XmlConfigDocument == null)
                throw new NullReferenceException("the xml document is null!");

            // Create a list of assembly paths
            List<string> assemblies = new List<string>();

            // Get the container element of the assembly list
            XmlNodeList elements = m_XmlConfigDocument.DocumentElement.GetElementsByTagName("assemblyReferences");

            // if we find the container element
            if (elements.Count > 0)
            {
                // get the assembly lisy
                XmlNodeList refs = elements[0].ChildNodes;

                // enumerate through the list of assemblies
                foreach (XmlNode node in refs)
                {
                    // get the assembly attributes
                    XmlAttributeCollection attributes = node.Attributes;

                    string fullPath = Environment.CurrentDirectory + "\\" + GetSafeAttributeValue(attributes.GetNamedItem("fileName"));
                    string version = GetSafeAttributeValue(attributes.GetNamedItem("version"));
                    string key = GetSafeAttributeValue(attributes.GetNamedItem("key"));
                    string name = GetSafeAttributeValue(attributes.GetNamedItem("name"));
                    EventWaitHandle m_LoadWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);

                    try
                    {
                        Assembly assembly = Assembly.ReflectionOnlyLoadFrom(fullPath);

                        // Check the name
                        if (!assembly.GetName().Name.Equals(name) && !String.Empty.Equals(name))
                            continue;

                        // Check version
                        if (!assembly.GetName().Version.ToString().Equals(version) && !String.Empty.Equals(version))
                            continue;

                        // Check the key
                        int dis;
                        if (!assembly.GetName().GetPublicKey().SequenceEqual(HexEncoding.GetBytes(key, out dis)) && !String.Empty.Equals(key))
                            continue;
                    }
                    catch (System.Exception)
                    {
                        continue;
                    }

                    // Added the assembly path to list
                    assemblies.Add(fullPath);
                }
            }

            return assemblies;
        }

        #region IConfigurationSystem Members

        public object GetConfig(string configKey)
        {
            foreach (XmlNode node in GetSettingElements())
            {
                XmlAttributeCollection attritbutes = node.Attributes;

                if (attritbutes.GetNamedItem("key").Value == configKey)
                {
                    return attritbutes.GetNamedItem("value").Value;
                }
            }

            return null;
        }

        public void Init()
        {
            m_SchemaFile = new FileStream(
                global::NEmuVirtualHost.Resources.Resources.DIRECTORY_PATH_SCHEMAS + global::NEmuVirtualHost.Resources.Resources.FILENAME_HOSTCONFIG_SCHEMA,
                FileMode.Open, FileAccess.Read, FileShare.Read);

            m_ConfigFile = new FileStream(global::NEmuVirtualHost.Resources.Resources.FILENAME_HOSTCONFIG_XML,
                FileMode.Open, FileAccess.Read, FileShare.Read);

            m_XmlConfigDocument = new XmlDocument();
            m_XmlConfigDocument.Schemas.Add(XmlSchema.Read(m_SchemaFile, new ValidationEventHandler(OnSchemaValidate)));
            m_XmlConfigDocument.Load(m_ConfigFile);
            m_XmlConfigDocument.Validate(new ValidationEventHandler(OnXmlValidate), m_XmlConfigDocument.DocumentElement);
        }

        #endregion

        #region IDisposable Members

        private void Dispose(bool disposing)
        {
            // If you need thread safety, use a lock around these 
            // operations, as well as in your methods that use the resource.
            if (!_disposed)
            {
                if (disposing)
                {
                    // Dispose resources here
                    m_ConfigFile.Close();
                    m_ConfigFile.Dispose();
                    m_SchemaFile.Close();
                    m_SchemaFile.Dispose();
                }

                // Indicate that the instance has been disposed.
                _disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);

            // Use SupressFinalize in case a subclass
            // of this type implements a finalizer.
            GC.SuppressFinalize(this);
        }

        #endregion

        #region IDictionary<> Members

        public void Add(string key, string value)
        {
            throw new NotSupportedException("cannot change a readonly state");
        }

        public bool ContainsKey(string key)
        {
            return GetConfig(key) != null;
        }

        public ICollection<string> Keys
        {
            get 
            {
                if (m_XmlConfigDocument != null)
                {

                    List<string> keys = new List<string>();
                    XmlNodeList elements = GetSettingElements();

                    foreach (XmlNode node in elements)
                    {
                        XmlAttributeCollection attritbutes = node.Attributes;

                        XmlNode key = attritbutes.GetNamedItem("key");
                        if (!String.Empty.Equals(key.Name))
                        {
                            keys.Add(key.Value);
                        }
                    }

                    return keys;
                }
                else
                {
                    throw new NullReferenceException("No xml document has been created!");
                }
            }
        }

        public bool Remove(string key)
        {
            throw new NotSupportedException("cannot change a readonly state");
        }

        public bool TryGetValue(string key, out string value)
        {
            object v = GetConfig(key);

            if (v != null)
            {
                value = (string)v;
                return true;
            }
            else
            {
                value = null;
                return false;
            }
        }

        public ICollection<string> Values
        {
            get
            {
                if (m_XmlConfigDocument != null)
                {

                    List<string> values = new List<string>();
                    XmlNodeList elements = GetSettingElements();

                    foreach (XmlNode node in elements)
                    {
                        XmlAttributeCollection attritbutes = node.Attributes;

                        XmlNode value = attritbutes.GetNamedItem("value");
                        if (!String.Empty.Equals(value.Name))
                        {
                            values.Add(value.Value);
                        }
                    }

                    return values;
                }
                else
                {
                    throw new NullReferenceException("No xml document has been created!");
                }
            }
        }

        public string this[string key]
        {
            get
            {
                return (string)GetConfig(key);
            }
            set
            {
                throw new NotSupportedException("cannot change the readonly state");
            }
        }

        public void Add(KeyValuePair<string, string> item)
        {
            throw new NotSupportedException("cannot change a readonly state");
        }

        public void Clear()
        {
            throw new NotSupportedException("cannot change the readonly state");
        }

        public bool Contains(KeyValuePair<string, string> item)
        {
            if (ContainsKey(item.Key))
            {
                string value;
                if (TryGetValue(item.Key, out value))
                {
                    if (item.Value.Equals(value))
                        return true;
                    else
                        return false;
                }
                else
                    return false;
            }
            else
                return false;
        }

        public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
        {
            if (array == null)
                throw new ArgumentNullException("array is null");

            if (arrayIndex < 0 || arrayIndex > array.Length)
                throw new IndexOutOfRangeException("arrayIndex is out of range");

            if (this.Keys.Count > (array.Length - arrayIndex))
                throw new ArgumentException("the number of elements to copy is greater than the source space");

            IEnumerator<KeyValuePair<string, string>> configEnumerator = this.GetEnumerator();

            for (int i = 0; i < Keys.Count; i++)
            {
                configEnumerator.MoveNext();
                array[i + arrayIndex] = configEnumerator.Current;
            }
        }

        public int Count
        {
            get { return Keys.Count; }
        }

        public bool IsReadOnly
        {
            get { return true; }
        }

        public bool Remove(KeyValuePair<string, string> item)
        {
            throw new NotSupportedException("cannot change a readonly state");
        }

        public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
        {
            return new HostConfigEnumerator(this);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }
}
